#ifndef ALFS_POINTERS_CHECKINGPTR_HPP
#define ALFS_POINTERS_CHECKINGPTR_HPP
#include    <alfs/config.hpp>

// File [alfs/pointers/CheckingPtr.hpp].
//
// alfs::CheckingPtr<>.
// A shared pointer where operator-> throws if the referred object has been destroyed.
//
// (c) copyright Alf P. Steinbach 2008.
// Use, modification and distribution are subject to the "New BSD License"
// as listed at <url: http://www.opensource.org/licenses/bsd-license.php>.

#include    "RefCountedDeleter.hpp"                         // alfs::RefCountedDeleter
#include    "DestructionEventSource.hpp"                    // alfs::destructionEvent::Source
#include    <alfs/stdx/type_traits.hpp>                     // alfs::IsConvertibleFromTo
#include    <alfs/language_support/exception_throwing.hpp>  // alfs::throwX
#include    <boost/utility/enable_if.hpp>                   // boost::enable_if
#include    <boost/mpl/if.hpp>                              // boost::mpl::if_
#include    <algorithm>                                     // std::swap

namespace alfs {

    template< typename T > class CheckingPtr;

    namespace detail {

#       define  ThisClass       AutoClearingRefCountedDeleter

        template< typename T >
        class ThisClass
            : public RefCountedDeleter<T>
            , protected destructionEvent::Source::IHandler  // onReferredObjectDestroyed
            , protected destructionEvent::Consumer          // event connection management
        {
        static_assert( (alfs::IsConvertibleFromTo< T*, alfs::destructionEvent::Source* >::yes), "" );
        // T must be directly or indirectly publicly derived from alfs::destructionEvent::Source.

        private:
            typedef RefCountedDeleter<T>        BaseClass;

            ThisClass( ThisClass const& );              // No such.
            ThisClass& operator=( ThisClass const& );   // No such.

            virtual void onReferredObjectDestroyed()
                throw()
            {
                BaseClass::clear();
            }

        protected:
            using BaseClass::referent;

            ThisClass( T* p )
                throw()
                : BaseClass( p )
            {
                connect( *referent(), *this );
            }

            virtual ~ThisClass() throw()
            {
                if( referent() != 0 ) {  disconnect( *referent() ); }
            }

        public:
            static ThisClass* createWith( T* p )
                ALFS_MAY_THROW
            {
                // TODO: Replace this with ownership-transfer smart pointer using alfs::destroyPointee.
                try
                {
                    return new ThisClass( p );
                }
                catch( ... )
                {
                    alfs::destroyPointee( p );
                    throw;
                }
            }
 
            virtual void* clear()
                throw()
            {
                // Just releases referent, doesn't destroy.
                if( referent() == 0 ) { return 0; }

                disconnect( *referent() );
                return BaseClass::clear();
            }
        };
#       undef   ThisClass

    }    // detail

    class CheckingPtr_Internals
    {
    protected:
        template< typename T >
        class ConversionSource
        {
        friend class CheckingPtr_Internals;
        private:
            CheckingPtr<T> const*  p;
            ConversionSource( CheckingPtr<T> const& ref ) throw(): p( &ref ) {}
        };

        template< typename T >
        static ConversionSource<T> makeConversionSource( CheckingPtr<T> const& ref )
            throw()
        {
            return ConversionSource<T>( ref );
        }

        template< typename T >
        static CheckingPtr<T> const* referentOf( ConversionSource<T> const& v ) throw()
        {
            return v.p;
        }
    };


#   define  ThisClass       CheckingPtr

    template< typename T > class AutoNewPtr;

    template< typename T >
    class ThisClass: protected CheckingPtr_Internals
    {
    template< typename > friend class ThisClass;

    private:
        typedef boost::intrusive_ptr<AbstractRefCountedDeleter>     DeleterPtr;
        typedef typename SansDestructionEventWrapper<T>::Type     PureT;

        PureT*      myPtr;
        DeleterPtr  myDeleterPtr;

        DeleterPtr const& deleterPtr() const throw() { return myDeleterPtr; }

    protected:
        template< typename U >
        ThisClass(
            T*                                          p,
            detail::AutoClearingRefCountedDeleter<U>&   deleter,
            typename boost::enable_if_c<
                    alfs::IsConvertibleFromTo< U*, T* >::yes
                >::type* = 0
            )
            throw()
            : myPtr( p )
            , myDeleterPtr( &deleter, false )
        {}

        template< typename U >
        ThisClass( T* p, ThisClass<U> const& managingPtr )
            throw()
            : myPtr( p )
            , myDeleterPtr( managingPtr.deleterPtr() )
        {}

    public:
        typedef AutoNewPtr<T>   Auto;
        typedef T               Pointee;

        // Default constructor makes void.
        ThisClass()
            throw()
            : myPtr( 0 ), myDeleterPtr()
        {}

        // Original ownership transfer to ThisClass.
        template< typename U >
        ThisClass(
            U* p,
            typename boost::enable_if_c<
                    alfs::IsConvertibleFromTo< U*, alfs::destructionEvent::Source* >::yes &&
                    alfs::IsConvertibleFromTo< U*, T* >::yes
                >::type* = 0
            )
            ALFS_MAY_THROW
            : myPtr( p )
            , myDeleterPtr(
                (p == 0? 0 : detail::AutoClearingRefCountedDeleter<U>::createWith( p )),
                false
                )
        {}

        // Logical "implicit upcast".
        template< typename U >
        ThisClass( ThisClass<U> const& other )
            throw()
            : myPtr( other.myPtr )
            , myDeleterPtr( other.myDeleterPtr )
        {}

        // Logical "pointer dynamic cast".
        template< typename U >
        ThisClass( ConversionSource<U> const& other )
            ALFS_MAY_THROW
            : myPtr( dynamic_cast<T*>( referentOf( other )->validRawPointerOrZero() ) )
            , myDeleterPtr( referentOf( other )->deleterPtr() )
        {
            if( myPtr == 0 ) { makeVoid(); }
        }

        ConversionSource<T> convertedOrVoid() const
            throw()
        {
            return makeConversionSource<T>( *this );
        }

        void swap( ThisClass& other )
            throw()
        {
            myDeleterPtr.swap( other.myDeleterPtr );
            std::swap( myPtr, other.myPtr );
        }

        // If this is last ThisClass instance with that referent, destroys referent.
        // Otherwise, affects only this ThisClass instance.
        void makeVoid()
            throw()
        {
            ThisClass().swap( *this );
        }

        bool isVoid() const
            throw()
        {
            bool const noDeleter = (myDeleterPtr.get() == 0);
            return noDeleter || myDeleterPtr->isVoid();
        }

        bool hasValidReferent() const
            throw()
        {
            return !isVoid();
        }

        // Does not destroy the referent.
        // Voids all ThisClass instances with that referent.
        void releaseReferent()
            throw()
        {
            if( hasValidReferent() ) { myDeleterPtr->clear(); }
            assert( isVoid() );
        }

        // TODO: replace result type with alfs::destroyPointee based ownership transfer pointer.
        T* releasedReferent()
            throw()
        {
            if( isVoid() ) { return 0; }

            T* const result = static_cast<T*>( myPtr );
            releaseReferent();
            return result;
        }

        PureT* operator->() const
            ALFS_MAY_THROW
        {
            // TODO: replace line below with customization point.
            (hasValidReferent())
                || throwX( "ThisClass::operator->: void referent (nullpointer)" );
            return myPtr;
        }

        // This one is for optimization.  Use with extreme caution.  Can yield UB,
        // and in particular can yield a non-zero pointer when logically isVoid().
        PureT* unchecked() const
            throw()
        {
            return myPtr;
        }

        PureT* validRawPointerOrZero() const
            throw()
        {
            return (isVoid()? 0 : myPtr);
        }

        PureT* validRawPointerOrX() const
            ALFS_MAY_THROW
        {
            // TODO: replace line below with customization point.
            (hasValidReferent())
                || throwX( "ThisClass::nonVoidRawPointer: void referent (nullpointer)" );
            return myPtr;
        }

        // For simple uniform treatment of smart pointers in templated code:
        CheckingPtr<T> asCheckingPtr() const
            throw()
        {
            return *this;
        }
    };
#   undef   ThisClass


    namespace detail{

        struct NewNoArgs {};
        struct New1Arg {};
    }  // detail
    detail::NewNoArgs const newNoArgs   = {};
    detail::New1Arg const   new1Arg     = {};



#   define  ThisClass       AutoNewPtr

    template< typename T >
    class ThisClass
        : public CheckingPtr< T >
    {
    template< typename > friend class ThisClass;

    protected:
        typedef typename WithDestructionEventWrapper<T>::Type   WrappedT;
        typedef CheckingPtr<T>                          BaseClass;

    public:
        // Logical "implicit upcast".
        template< typename U >
        ThisClass(
            ThisClass<U> const& other
            // MSVC 7.1 thinks constructors are ambigious or non-existent (it doesn't say which)
            // if enable_if is used here, or even if there is any defaulted argument, which hints
            // at template specificity.  So if the intent is to new a T with an AutoNewPtr<U> as
            // constructor argument, then use an explicit 'new1Arg' to disambiguate.  Anyway,
            // enable_if, while convenient, would not solve that case with U derived from T.
            )
            throw()
            : BaseClass( other )
        {}

        //// Logical "implicit downcast" (or "up-referent-and-down-wrapper-cast"), special case.
        //template< typename U >
        //ThisClass(
        //    CheckingPtr<U> const&       other,
        //    typename boost::enable_if_c<
        //            alfs::IsConvertibleFromTo< U*, T* >::yes
        //        >::type* = 0    // For contorted case where this decides incorrectly, use 'new1Arg'.
        //    )
        //    throw()
        //    : BaseClass( other )
        //{}

        // Logical "pointer dynamic cast".
        template< typename U >
        ThisClass( typename BaseClass::template ConversionSource<U> const& other )
            throw()
            : BaseClass( other )
        {}

        // Void pointer as default is in support of using ThisClass's in e.g. arrays.
        ThisClass()
            throw()
            : BaseClass()
        {}

        // This constructor is not instantiated if it's not used (we're in magic template
        // world).  I.e. it does not impose a requirement of T being default-constructible.
        ThisClass( detail::NewNoArgs )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT )
        {}

        template< typename U >
        ThisClass( U const& u, detail::New1Arg = new1Arg )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT( u ) )
        {}

        template< typename U, typename V >
        ThisClass( U const& u, V const& v )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT( u, v ) )
        {}

        template< typename U, typename V, typename W >
        ThisClass( U const& u, V const& v, W const& w )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT( u, v, w ) )
        {}

        template< typename U, typename V, typename W, typename X >
        ThisClass( U const& u, V const& v, W const& w, X const& x )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT( u, v, w, x ) )
        {}

        template< typename U, typename V, typename W, typename X, typename Y >
        ThisClass( U const& u, V const& v, W const& w, X const& x, Y const& y )
            ALFS_MAY_THROW
            : BaseClass( new WrappedT( u, v, w, x, y ) )
        {}
    };
#   undef   ThisClass

}  // alfs

#endif
